#ifndef BASIC_PROVIDER_HPP
#define BASIC_PROVIDER_HPP

#include <type_traits>
#include "content/data_provider.hpp"
#include "utils/safe_value.hpp"

// template <typename T>
// struct basic_data;
//
// template <typename T>
// using is_std_string_t = typename std::is_same<std::string, T>::type;
//
// template <typename T>
// using is_boolean_t = typename std::is_same<bool, T>::type;
//
// template <typename T>
// using is_signed_t = typename std::is_signed<T>::type;
//
// template <typename T>
// using is_unsigned_t = typename std::is_unsigned<T>::type;
//
// template <typename T>
// using is_floating_point_t = typename std::is_floating_point<T>::type;

/*
#define DEFINE_BUILTIN_COND(s_value, r_type, fn_name, type_tag) \
    r_type get_##fn_name() override { \
        return get_##fn_name##_impl(&s_value, type_tag()); \
    } \
    void set_##fn_name(r_type v) override { \
        set_##fn_name##_impl(&s_value, v, type_tag()); \
    }

#define FALSE_IMPL(t,n) \
    t    get_##n##_impl(void *, std::false_type)      { return data_provider::get_##n(); } \
    void set_##n##_impl(void *, t v, std::false_type) { data_provider::set_##n(v); }

#define TRUE_IMPL(r,s,n) \
    r    get_##n##_impl(void *d, std::true_type)       { return (r)*(s*)d; } \
    void set_##n##_impl(void *d, r v, std::true_type)  { *(s*)d = (s)v; }

#define DEFINE_BUILTIN_DATA(s_type, category) \
template<> struct basic_data<s_type> final: public data_provider { \
    s_type value; \
    data_category type() const override { return category; } \
    \
    DEFINE_BUILTIN_COND(value, s64, s64, is_signed_t<s_type>) \
    \
    DEFINE_BUILTIN_COND(value, u64, u64, is_unsigned_t<s_type>) \
    \
    DEFINE_BUILTIN_COND(value, f64, f64, is_floating_point_t<s_type>) \
    \
    DEFINE_BUILTIN_COND(value, bool, boolean, is_boolean_t<s_type>) \
    \
    DEFINE_BUILTIN_COND(value, std::string, std_string, is_std_string_t<s_type>) \
    enum { vt = value_traits<s_type>::store_type }; \
private: \
    FALSE_IMPL(s64,s64) \
    FALSE_IMPL(u64,u64) \
    FALSE_IMPL(f64,f64) \
    FALSE_IMPL(bool,boolean) \
    FALSE_IMPL(std::string,std_string) \
    TRUE_IMPL(s64, s_type, s64) \
    TRUE_IMPL(u64, s_type, u64) \
    TRUE_IMPL(f64, s_type, f64) \
    TRUE_IMPL(bool, s_type, boolean) \
    TRUE_IMPL(std::string, s_type, std_string) \
}

DEFINE_BUILTIN_DATA(s8 , signed_integral);
DEFINE_BUILTIN_DATA(s16, signed_integral);
DEFINE_BUILTIN_DATA(s32, signed_integral);
DEFINE_BUILTIN_DATA(s64, signed_integral);

DEFINE_BUILTIN_DATA(u8 , unsigned_integral);
DEFINE_BUILTIN_DATA(u16, unsigned_integral);
DEFINE_BUILTIN_DATA(u32, unsigned_integral);
DEFINE_BUILTIN_DATA(u64, unsigned_integral);

DEFINE_BUILTIN_DATA(f32, floating_point);
DEFINE_BUILTIN_DATA(f64, floating_point);

DEFINE_BUILTIN_DATA(bool, boolean);
DEFINE_BUILTIN_DATA(std::string, std_string);


#undef DEFINE_BUILTIN_COND
#undef DEFINE_BUILTIN_DATA

*/

template <typename T>
concept signed_t = std::is_signed_v<T>;

template <typename T>
concept unsigned_t = std::is_unsigned_v<T>;

template <typename T>
concept floating_point_t = std::is_floating_point_v<T>;

template <typename T>
concept boolean_t = std::is_same_v<bool, T>;

template <typename T>
concept std_string_t = std::is_same_v<std::string, T>;

template<typename T>
struct basic_data;

template<typename s> requires signed_t<s>
struct basic_data<s>: data_provider
{
    s value;

    // type
    data_category type() const override { return signed_integral; }
    
    // set
    void set_u64(u64 v) override { value = (s)v; }
    void set_s64(s64 v) override { value = (s)v; }
    void set_f64(f64 v) override { value = (s)v; }
    void set_boolean(bool v) override { value = (s)v; }
    void set_std_string(std::string v) override { value = (s)strtoll(v.c_str(), nullptr, 0); }

    // get
    s64 get_s64() override { return (s64)value; }
    u64 get_u64() override { return (u64)value; }
    f64 get_f64() override { return (f64)value; }
    bool get_boolean() override { return (bool)value; }
    std::string get_std_string() override { return std::to_string(value); }
};

template<typename s> requires unsigned_t<s>
struct basic_data<s> : data_provider
{
    s value;

    // type
    data_category type() const override { return unsigned_integral; }

    // set
    void set_u64(u64 v) override { value = (s)v; }
    void set_s64(s64 v) override { value = (s)v; }
    void set_f64(f64 v) override { value = (s)v; }
    void set_boolean(bool v) override { value = (s)v; }
    void set_std_string(std::string v) override { value = (s)strtoull(v.c_str(), nullptr, 0); }

    // get
    s64 get_s64() override { return (s64)value; }
    u64 get_u64() override { return (u64)value; }
    f64 get_f64() override { return (f64)value; }
    bool get_boolean() override { return (bool)value; }
    std::string get_std_string() override { return std::to_string(value); }
};

template<typename s> requires floating_point_t<s>
struct basic_data<s> : data_provider
{
    s value;

    // type
    data_category type() const override { return floating_point; }

    // set
    void set_u64(u64 v) override { value = (s)v; }
    void set_s64(s64 v) override { value = (s)v; }
    void set_f64(f64 v) override { value = (s)v; }
    void set_boolean(bool v) override { value = (s)v; }
    void set_std_string(std::string v) override { value = (s)strtod(v.c_str(), nullptr); }

    // get
    s64 get_s64() override { return (s64)value; }
    u64 get_u64() override { return (u64)value; }
    f64 get_f64() override { return (f64)value; }
    bool get_boolean() override { return (bool)value; }
    std::string get_std_string() override { return std::to_string(value); }
};

template<typename s> requires boolean_t<s>
struct basic_data<s> : data_provider
{
    s value;

    // set
    void set_u64(u64 v) override { value = (s)v; }
    void set_s64(s64 v) override { value = (s)v; }
    void set_f64(f64 v) override { value = (s)v; }
    void set_boolean(bool v) override { value = (s)v; }
    void set_std_string(std::string v) override { value = (s)strtoll(v.c_str(), nullptr, 0); }

    // get
    s64 get_s64() override { return (s64)value; }
    u64 get_u64() override { return (u64)value; }
    f64 get_f64() override { return (f64)value; }
    bool get_boolean() override { return (bool)value; }
    std::string get_std_string() override { return std::to_string(value); }
};

template<typename s> requires std_string_t<s>
struct basic_data<s> : data_provider
{
    s value;

    // set
    void set_u64(u64 v) override { value = std::to_string(v); }
    void set_s64(s64 v) override { value = std::to_string(v); }
    void set_f64(f64 v) override { value = std::to_string(v); }
    void set_boolean(bool v) override { value = std::to_string(v); }
    void set_std_string(std::string v) override { value = v; }

    // get
    s64 get_s64() override { return strtoll(value.c_str(), nullptr, 0); }
    u64 get_u64() override { return strtoull(value.c_str(), nullptr, 0); }
    f64 get_f64() override { return strtod(value.c_str(), nullptr); }
    bool get_boolean() override { return parse_bool(value); }
    std::string get_std_string() override { return value; }
};

#endif /* BASIC_PROVIDER_HPP */
